package monitor_platon

import (
	"bytes"
	"context"
	"crypto/ecdsa"
	"encoding/json"
	"fmt"
	platon "github.com/PlatONnetwork/PlatON-Go"
	"github.com/PlatONnetwork/PlatON-Go/common"
	"github.com/PlatONnetwork/PlatON-Go/common/hexutil"
	"github.com/PlatONnetwork/PlatON-Go/common/vm"
	"github.com/PlatONnetwork/PlatON-Go/core/types"
	"github.com/PlatONnetwork/PlatON-Go/crypto"
	"github.com/PlatONnetwork/PlatON-Go/ethclient"
	"github.com/PlatONnetwork/PlatON-Go/p2p/discover"
	"github.com/PlatONnetwork/PlatON-Go/rlp"
	"go.uber.org/zap"
	"math/big"
	"platon-tools/go-service/event"
	go_logger "platon-tools/go-service/go-logger"
	"platon-tools/go-service/monitor/utile"
	"platon-tools/go-service/protocol/rpc_pb"
	"time"
)

const (
	settlementPeriod = uint64(10750)
	consensusPeriod  = uint64(430)
	rpcTimeout       = 10 * time.Second
)

type eventMsg struct {
	msgData       interface{}
	blockNumber   uint64
	blockReward   uint64
	stakingReward uint64
	miner         string
	bse           bool
	cp            uint64
	sp            uint64
}

type Node struct {
	*ethclient.Client

	PlatOnServer     string
	ContractsCreated int64
	TokenTransfers   int64
	ContractCalls    int64
	EthTransfers     int64
	BlockSize        float64
	LoadTime         float64
	TotalEth         *big.Int
	CurrentBlock     *types.Block
	Sync             *platon.SyncProgress
	LastBlockUpdate  time.Time
	SugGasPrice      *big.Int
	PendingTx        uint
	NetworkId        *big.Int

	ctx    context.Context
	cancel context.CancelFunc

	config     *Config
	nodeId     discover.NodeID
	privateKey *ecdsa.PrivateKey
	from       common.Address

	currentCP          uint64
	currentSP          uint64
	currentBlockReward uint64

	feed    *event.Event
	eventCh chan eventMsg
}

type Config struct {
	PlatOnServer string
	HttpServer   string
	NodeID       string
	SpecialNode  bool //特殊节点，只负责从指定的节点读取内部合约信息。
	Delay        int
}

func NewNode(ctx context.Context, config *Config, feed *event.Event) *Node {
	c, cancel := context.WithCancel(ctx)
	return &Node{
		ctx:       c,
		cancel:    cancel,
		TotalEth:  big.NewInt(0),
		config:    config,
		currentCP: 0,
		currentSP: 0,
		feed:      feed,
		eventCh:   make(chan eventMsg, 20),
	}
}

func (s *Node) Start() error {
	privateKey, err := crypto.GenerateKey()
	if err != nil {
		go_logger.Logger.Error("generate key", zap.Error(err))
		return err
	}

	s.privateKey = privateKey
	s.from = crypto.PubkeyToAddress(s.privateKey.PublicKey)

	s.Client, err = ethclient.DialContext(s.ctx, s.config.PlatOnServer)
	if err != nil {
		go_logger.Logger.Error("DialContext", zap.Error(err))
		return err
	}

	s.CurrentBlock, err = s.Client.BlockByNumber(s.ctx, nil)
	if err != nil {
		go_logger.Logger.Error("BlockByNumber", zap.Error(err))
		return err
	}

	//go s.runLoop(errc)
	go s.chainDataTrans()
	go s.subscribeLoop()

	return nil
}

func (s *Node) subscribeLoop() {
	defer s.cancel()

	subCtx, cancel := context.WithCancel(s.ctx)

	go s.runSubscribe(subCtx, cancel)

	for {
		select {
		case <-s.ctx.Done():
			return
		case <-subCtx.Done():
			subCtx, cancel = context.WithCancel(s.ctx)
			go s.runSubscribe(subCtx, cancel)
		}
	}

}

func (s *Node) runSubscribe(subCtx context.Context, cancelFunc context.CancelFunc) {
	defer cancelFunc()
	headCh := make(chan *types.Header, 10)
	defer close(headCh)

	subHead, err := s.Client.SubscribeNewHead(subCtx, headCh)
	if err != nil {
		go_logger.Logger.Error("SubscribeNewHead", zap.Error(err))
		return
	}

	for {
		select {
		case <-subCtx.Done():
			go_logger.Logger.Sugar().Infof("subscribe quit %s", time.Now().String())
			return
		case err, ok := <-subHead.Err():
			if ok {
				go_logger.Logger.Error("sub head err", zap.Error(err))
				return
			}
		case head, ok := <-headCh:
			if ok {
				cp, sp := s.calculatePeriod(head.Number.Uint64())
				ce, se := s.calculatePeriodEnd(head.Number.Uint64())

				if ce == 411 {
					//go_logger.Logger.Sugar().Infof("共识周期411 块高={ %d } 共识周期={ %d } 第{ %d }共识块  结算周期={ %d } 第{ %d } 结算块", head.Number.Uint64(), cp, ce, sp, se)
					sBlockReward, err := s.getCurrentCyclePackageReward()
					if err != nil {
						go_logger.Logger.Error("GetPackageReward", zap.Error(err))
						return
					}

					if blockReward, err := hexutil.DecodeBig(*sBlockReward); err == nil {
						s.currentBlockReward = blockReward.Uint64()
					} else {
						go_logger.Logger.Error("decode block reward", zap.Error(err))
					}

					go_logger.Logger.Sugar().Infof("共识周期411 块高={ %d } 块奖励={ %d } 共识周期={ %d } 第{ %d }共识块  结算周期={ %d } 第{ %d } 结算块", head.Number.Uint64(), s.currentBlockReward, cp, ce, sp, se)

					if validator, err := s.getCurrentConsensusValidatorList(); err != nil {
						go_logger.Logger.Error("get consensus validator list", zap.Error(err))
					} else {
						s.eventCh <- eventMsg{
							msgData:     validator,
							blockNumber: head.Number.Uint64(),
							cp:          cp,
							sp:          sp,
							blockReward: s.currentBlockReward,
							miner:       head.Coinbase.Bech32(),
						}
					}

				} else if settlementPeriod == se {
					// 共识轮结束时获取当前公识轮信息
					sStakingReward, err := s.getCurrentCycleStakingReward()
					if err != nil {
						go_logger.Logger.Error("GetPackageReward", zap.Error(err))
						return
					}

					stakingReward, err := hexutil.DecodeBig(*sStakingReward)
					if err != nil {
						go_logger.Logger.Error("decode block reward", zap.Error(err))
					}

					go_logger.Logger.Sugar().Infof("结算周期结束 块高={ %d } 结算奖励={ %d } 共识周期={ %d } 第{ %d }共识块  结算周期={ %d } 第{ %d } 结算块", head.Number.Uint64(), stakingReward.Uint64(), cp, ce, sp, se)

					if validator, err := s.getCurrentCycleVerifierList(); err != nil {
						go_logger.Logger.Error("get verifier list", zap.Error(err))
					} else {
						s.eventCh <- eventMsg{
							msgData:       validator,
							stakingReward: stakingReward.Uint64(),
							cp:            cp,
							sp:            sp,
							blockReward:   s.currentBlockReward,
							miner:         head.Coinbase.Bech32(),
							bse:           true,
						}
					}
				}

			}
		}
	}
}

func (s *Node) runLoop() {
	defer s.cancel()
	var (
		lastBlock *types.Block
		err       error
	)
	duration := time.Duration(s.config.Delay) * time.Millisecond
	trace := time.NewTicker(duration)
	defer trace.Stop()

	for {
		select {
		case <-s.ctx.Done():
			return
		case <-trace.C:
			start := time.Now()
			lastBlock, err = s.Client.BlockByNumber(s.ctx, nil)
			if err != nil {
				go_logger.Logger.Sugar().Warn("get last block failed", err)
				return
			}

			if lastBlock == nil || s.CurrentBlock.NumberU64() < lastBlock.NumberU64() {
				s.LastBlockUpdate = time.Now()
				s.LoadTime = time.Now().Sub(start).Seconds()
			} else {
				go_logger.Logger.Sugar().Debug("current block", s.CurrentBlock.NumberU64(), "last block", lastBlock.NumberU64())
				continue
			}

			s.CurrentBlock = lastBlock
			go_logger.Logger.Sugar().Debug("current block", s.CurrentBlock.NumberU64())

			s.SugGasPrice, _ = s.Client.SuggestGasPrice(s.ctx)
			s.PendingTx, _ = s.Client.PendingTransactionCount(s.ctx)
			s.NetworkId, _ = s.Client.NetworkID(s.ctx)
			s.Sync, _ = s.Client.SyncProgress(s.ctx)

			trace.Reset(duration)
		}
	}
}

func (s *Node) calculatePeriod(blockNumber uint64) (cPeriod uint64, sPeriod uint64) {
	cPeriod = (blockNumber / consensusPeriod) + 1
	sPeriod = (blockNumber / settlementPeriod) + 1
	return
}

func (s *Node) calculatePeriodEnd(blockNumber uint64) (cEnd uint64, sEnd uint64) {
	cEnd = blockNumber % consensusPeriod
	sEnd = blockNumber % settlementPeriod
	return
}

func (s *Node) Close() error {
	select {
	case <-s.ctx.Done():
	default:
		s.Client.Close()
		s.cancel()
	}

	return nil
}

func (s *Node) chainDataTrans() {
	defer close(s.eventCh)
	for {
		select {
		case <-s.ctx.Done():
			return
		case msg, ok := <-s.eventCh:
			if ok {
				go_logger.Logger.Sugar().Debugf("收到合约查询通知")
				if ve, ok := msg.msgData.(utile.ValidatorExQueue); ok {
					if msg.bse {
						s.genVerifierList(msg, ve)
					} else {
						s.genValidatorList(msg, ve)
					}

				}
			}
		}
	}
}

func (s *Node) genValidatorList(msg eventMsg, ve utile.ValidatorExQueue) {
	value := utile.ToEther(big.NewInt(0).SetUint64(msg.stakingReward))
	blockReward, _ := value.Float64()
	vlist := event.ValidatorList{
		CurrentBlock:     msg.blockNumber,
		ConsensusNo:      msg.cp,
		Epoch:            msg.sp,
		MiningReward:     blockReward,
		CurrentBlockNode: msg.miner,
		NodesList:        []event.Validator{},
	}

	for _, v := range ve {
		var (
			sAccount float64 = 0
			bAccount float64 = 0
		)

		ctx, _ := context.WithTimeout(s.ctx, rpcTimeout)
		stakingAddr, _ := common.Bech32ToAddress(v.StakingAddress)

		if v, err := s.Client.BalanceAt(ctx, stakingAddr, nil); err != nil {
			value := utile.ToEther(v)
			sAccount, _ = value.Float64()
		}

		ctx, _ = context.WithTimeout(s.ctx, rpcTimeout)
		benefitAddr, _ := common.Bech32ToAddress(v.BenefitAddress)
		if v, err := s.Client.BalanceAt(ctx, benefitAddr, nil); err != nil {
			value := utile.ToEther(v)
			bAccount, _ = value.Float64()
		}

		bigShares, _ := hexutil.DecodeBig(v.Shares)
		shares, _ := utile.ToEther(bigShares).Float64()

		valdator := event.Validator{
			NodeId:             v.NodeId,
			NodeName:           v.NodeName,
			StakingAddr:        v.StakingAddress,
			StakingAddrAccount: sAccount,
			BenefitAddr:        v.BenefitAddress,
			BenefitAddrAccount: bAccount,
			TotalBalance:       shares,
			NodeStatus:         0,
			BonusRatio:         v.RewardPer,
			Extra:              "",
		}

		id := discover.MustHexID(v.NodeId)
		stakingInfo, err := s.getCandidateInfo(id.Bytes())
		if err != nil {
			go_logger.Logger.Warn("get candidate info failed", zap.Error(err))
		} else {
			r, _ := hexutil.DecodeBig(stakingInfo.Released)
			rh, _ := hexutil.DecodeBig(stakingInfo.ReleasedHes)
			rp, _ := hexutil.DecodeBig(stakingInfo.RestrictingPlan)
			rph, _ := hexutil.DecodeBig(stakingInfo.RestrictingPlanHes)

			dt, _ := hexutil.DecodeBig(stakingInfo.DelegateTotal)
			dth, _ := hexutil.DecodeBig(stakingInfo.DelegateTotalHes)

			ss := r.Uint64() + rh.Uint64() + rp.Uint64() + rph.Uint64()
			ds := dt.Uint64() + dth.Uint64()

			go_logger.Logger.Sugar().Debugf("质押量={ %d }	委托={ %d }", ss, ds)

			valdator.StakingSum, _ = utile.ToEther(big.NewInt(0).SetUint64(ss)).Float64()
			valdator.DelegationSum, _ = utile.ToEther(big.NewInt(0).SetUint64(ds)).Float64()
		}

		vlist.NodesList = append(vlist.NodesList, valdator)
	}

	data, err := json.Marshal(vlist)
	if err != nil {
		go_logger.Logger.Error("marshal candidate list failed", zap.Error(err))
		return
	}

	go_logger.Logger.Sugar().Debugf("共识周期 =【 %s 】", string(data))

	s.feed.Send(event.MonitorEvent{
		Name: rpc_pb.MsgType_Validator,
		Data: data,
	})
}

func (s *Node) genVerifierList(msg eventMsg, ve utile.ValidatorExQueue) {
	value := utile.ToEther(big.NewInt(0).SetUint64(msg.stakingReward))
	stakingReward, _ := value.Float64()

	vlist := event.VerifierList{
		CurrentBlock:     msg.blockNumber,
		ConsensusNo:      msg.cp,
		Epoch:            msg.sp,
		StakingReward:    stakingReward,
		CurrentBlockNode: msg.miner,
		NodesList:        []event.Verifier{},
	}

	for _, v := range ve {
		var (
			sAccount float64 = 0
			bAccount float64 = 0
		)

		ctx, _ := context.WithTimeout(s.ctx, 2*time.Second)
		stakingAddr, _ := common.Bech32ToAddress(v.StakingAddress)

		if v, err := s.Client.BalanceAt(ctx, stakingAddr, nil); err != nil {
			value := utile.ToEther(v)
			sAccount, _ = value.Float64()
		}

		ctx, _ = context.WithTimeout(s.ctx, 2*time.Second)
		benefitAddr, _ := common.Bech32ToAddress(v.BenefitAddress)
		if v, err := s.Client.BalanceAt(ctx, benefitAddr, nil); err != nil {
			value := utile.ToEther(v)
			bAccount, _ = value.Float64()
		}

		bigShares, _ := hexutil.DecodeBig(v.Shares)
		shares, _ := utile.ToEther(bigShares).Float64()

		valdator := event.Verifier{
			NodeId:             v.NodeId,
			NodeName:           v.NodeName,
			StakingAddr:        v.StakingAddress,
			StakingAddrAccount: sAccount,
			BenefitAddr:        v.BenefitAddress,
			BenefitAddrAccount: bAccount,
			BonusRatio:         v.RewardPer,
			TotalBalance:       shares,
			StakingSum:         0.0,
			DelegationSum:      0.0,
			ValidatorTerm:      v.ValidatorTerm,
			NodeStatus:         0,
			Extra:              "",
		}

		id := discover.MustHexID(v.NodeId)
		go_logger.Logger.Sugar().Debugf("nodeid: 【%s】--【%s】-- toString【%s】", v.NodeId, id.String(), id.GoString())
		//s.getCandidateInfo()
		stakingInfo, err := s.getCandidateInfo(id.Bytes())
		if err != nil {
			go_logger.Logger.Warn("get candidate info failed", zap.Error(err))
		} else {
			r, _ := hexutil.DecodeBig(stakingInfo.Released)
			rh, _ := hexutil.DecodeBig(stakingInfo.ReleasedHes)
			rp, _ := hexutil.DecodeBig(stakingInfo.RestrictingPlan)
			rph, _ := hexutil.DecodeBig(stakingInfo.RestrictingPlanHes)

			dt, _ := hexutil.DecodeBig(stakingInfo.DelegateTotal)
			dth, _ := hexutil.DecodeBig(stakingInfo.DelegateTotalHes)

			ss := r.Uint64() + rh.Uint64() + rp.Uint64() + rph.Uint64()
			ds := dt.Uint64() + dth.Uint64()

			go_logger.Logger.Sugar().Debugf("质押量={ %d }	委托={ %d }", ss, ds)

			valdator.StakingSum, _ = utile.ToEther(big.NewInt(0).SetUint64(ss)).Float64()
			valdator.DelegationSum, _ = utile.ToEther(big.NewInt(0).SetUint64(ds)).Float64()
		}

		vlist.NodesList = append(vlist.NodesList, valdator)
	}
	data, err := json.Marshal(vlist)
	if err != nil {
		go_logger.Logger.Error("marshal candidate list failed", zap.Error(err))
		return
	}

	go_logger.Logger.Sugar().Debugf("结算周期 =【 %s 】", string(data))

	s.feed.Send(event.MonitorEvent{
		Name: rpc_pb.MsgType_Validator,
		Data: data,
	})
}

func encodePPOS(funcType uint16, params ...[]byte) ([]byte, common.Address) {
	par := buildParams(funcType, params...)
	buf := new(bytes.Buffer)
	err := rlp.Encode(buf, par)
	if err != nil {
		panic(fmt.Errorf("encode rlp data fail: %v", err))
	}
	return buf.Bytes(), funcTypeToContractAddress(funcType)
}

func buildParams(funcType uint16, params ...[]byte) [][]byte {
	var res [][]byte
	res = make([][]byte, 0)
	fnType, _ := rlp.EncodeToBytes(funcType)
	res = append(res, fnType)
	for _, param := range params {
		val, err := rlp.EncodeToBytes(param)
		if err != nil {
			panic(err)
		}
		res = append(res, val)
	}
	return res
}

func funcTypeToContractAddress(funcType uint16) common.Address {
	toadd := common.ZeroAddr
	switch {
	case 0 < funcType && funcType < 2000:
		toadd = vm.StakingContractAddr
	case funcType >= 2000 && funcType < 3000:
		toadd = vm.GovContractAddr
	case funcType >= 3000 && funcType < 4000:
		toadd = vm.SlashingContractAddr
	case funcType >= 4000 && funcType < 5000:
		toadd = vm.RestrictingContractAddr
	case funcType >= 5000 && funcType < 6000:
		toadd = vm.DelegateRewardPoolAddr
	}
	return toadd
}

/*
chqe, err := s.getCurrentCandidateList()
				if err != nil {
					go_logger.Logger.Error("getCurrentCandidateList", zap.Error(err))
					return
				}

				sStakingReward, err := s.getCurrentCycleStakingReward()
				if err != nil {
					go_logger.Logger.Error("getCurrentCycleStakingReward", zap.Error(err))
					return
				}

				sBlockReward, err := s.getCurrentCyclePackageReward();
				if err != nil {
					go_logger.Logger.Error("GetPackageReward", zap.Error(err))
					return
				}
				coinbase := s.CurrentBlock.Header().Coinbase.Bech32()

				stakingReward, _ := hexutil.DecodeBig(*sStakingReward)
				blockReward, _ := hexutil.DecodeBig(*sBlockReward)

				var items event.BlockChainDataTrans

				for _, v := range chqe {
					StakingAddrAccount := big.NewInt(0)
					if saddr, err := common.Bech32ToAddress(v.StakingAddress); err == nil {
						StakingAddrAccount, err = s.Client.BalanceAt(s.ctx, saddr, nil)
					}
					BenefitAddrAccount := big.NewInt(0)
					if baddr, err := common.Bech32ToAddress(v.BenefitAddress); err == nil {
						BenefitAddrAccount, err = s.Client.BalanceAt(s.ctx, baddr, nil)
					}


					total, _ := hexutil.DecodeBig(v.DelegateTotal)
					hes, _ := hexutil.DecodeBig(v.DelegateTotalHes)
					DelegationSum := total.Uint64() + hes.Uint64()

					released, _ := hexutil.DecodeBig(v.Released)
					releasedHes, _ := hexutil.DecodeBig(v.ReleasedHes)
					plan, _ := hexutil.DecodeBig(v.RestrictingPlan)
					planHes, _ := hexutil.DecodeBig(v.RestrictingPlanHes)

					StakingSum := released.Uint64() + releasedHes.Uint64() + plan.Uint64() + planHes.Uint64()

					data, err := json.Marshal(v)
					if err != nil {
						data = []byte("")
					}

					item := event.NodeContractItem{
						NodeId:               v.NodeId,
						CurrentBlock:         s.CurrentBlock.NumberU64(),
						ConsensusNo:          s.currentCP,
						Epoch:                s.currentSP,
						NodeName:             v.DescriptionEx.NodeName,
						StakingAddr:          v.StakingAddress,
						StakingAddrAccount:   StakingAddrAccount.Uint64(),
						BenefitAddr:          v.BenefitAddress,
						BenefitAddrAccount:   BenefitAddrAccount.Uint64(),
						BonusRatio:           int(v.RewardPer),
						DelegatorSum:         v.DelegateTotal,
						TotalBalance:         v.Shares,
						MiningReward:         blockReward.Uint64(),
						DelegationReward:     0,
						StakingReward:        stakingReward.Uint64(),
						StakingSum:           StakingSum,
						DelegationSum:        DelegationSum,
						DelegationNotReceive: 0,
						CurrentBlockNode:     coinbase,
						NodeStatus:           int(v.Status),
						Extra:                string(data),
					}

					items.NodesList = append(items.NodesList, item)
				}

				go_logger.Logger.Sugar().Debugf("666666")

				data, err := json.Marshal(items)
				if err != nil {
					go_logger.Logger.Error("marshal candidate list failed", zap.Error(err))
					continue
				}
				go_logger.Logger.Sugar().Info("blockChainDataTrans", string(data))

				s.feed.Send(event.MonitorEvent{
					Name: "BlockChainDataTrans",
					Data: data,
				})

				go_logger.Logger.Sugar().Debugf("77777")

				//go_logger.Logger.Sugar().Info("send MsgType_QueryCandidateList")
				//data, err := json.Marshal(chqe)
				//if err != nil {
				//	go_logger.Logger.Error("marshal candidate list failed", zap.Error(err))
				//	return
				//}

*/
